package spring.data.jpa.transform;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.beanutils.BeanUtilsBean2;
import org.apache.commons.beanutils.Converter;
import org.hibernate.HibernateException;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;

import spring.data.jpa.annotation.ColumnMapper;

public class BeanResultTransformer extends AliasedTupleSubsetResultTransformer {

    private static final long serialVersionUID = 8506252746469223328L;
    /**
     * The Constant CACHED_COPIER_MAP.
     */
    private final Map<String, SetterInvok> aliasesMap = new ConcurrentHashMap<>();

    private Class<?> resultClass;

    public BeanResultTransformer(Class<?> resultClass) {
        if (resultClass == null) {
            throw new IllegalArgumentException("resultClass cannot be null");
        }
        this.resultClass = resultClass;
        Field[] fields = resultClass.getDeclaredFields();
        if (fields.length == 0) {
            throw new IllegalArgumentException("resultClass have no mapper to set");
        }
        for (Field field : fields) {
            ColumnMapper mapper = field.getAnnotation(ColumnMapper.class);
            if (mapper != null) {
                aliasesMap.put(mapper.columnName().toUpperCase(), new SetterInvok(field.getName(), field.getType(),
                        mapper.converter()));
            }
        }
    }

    @Override
    public Object transformTuple(Object[] tuple, String[] aliases) {
        PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
                PropertyAccessStrategyBasicImpl.INSTANCE, PropertyAccessStrategyFieldImpl.INSTANCE,
                PropertyAccessStrategyMapImpl.INSTANCE);
        Object result;
        try {
            result = this.resultClass.newInstance();
            for (int i = 0; i < aliases.length; i++) {
                SetterInvok invok = aliasesMap.get(aliases[i].toUpperCase());
                Setter setter = propertyAccessStrategy.buildPropertyAccess(this.resultClass, invok.getFieldName())
                        .getSetter();
                Object value = tuple[i];
                if (invok.getConverter() != null && !invok.getConverter().isInterface()) {
                    value = BeanUtilsBean2.getInstance().getConvertUtils().convert(value, invok.getFieldType());
                }
                setter.set(result, value, null);
            }
        } catch (InstantiationException | IllegalAccessException e) {
            throw new HibernateException("Could not instantiate resultclass: " + resultClass.getName());
        }
        return result;
    }

    @Override
    public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
        return false;
    }

    class SetterInvok {
        private String fieldName;
        private Class<?> fieldType;
        private Class<? extends Converter> converter;

        public SetterInvok(String fieldName, Class<?> fieldType, Class<? extends Converter> converter) {
            super();
            this.fieldName = fieldName;
            this.fieldType = fieldType;
            this.converter = converter;
        }

        /**  
         * 获取fieldName  
         * @return fieldName
         */
        public String getFieldName() {
            return fieldName;
        }

        /**  
         * 设置fieldName  
         * @param fieldName
         */
        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        /**  
         * 获取converter  
         * @return converter
         */
        public Class<? extends Converter> getConverter() {
            return converter;
        }

        /**  
         * 设置converter  
         * @param converter
         */
        public void setConverter(Class<? extends Converter> converter) {
            this.converter = converter;
        }

        /**  
         * 获取fieldType  
         * @return fieldType
         */
        public Class<?> getFieldType() {
            return fieldType;
        }

        /**  
         * 设置fieldType  
         * @param fieldType
         */
        public void setFieldType(Class<?> fieldType) {
            this.fieldType = fieldType;
        }

    }
}
